package raft.message;

import raft.storage.LogEntry;

import java.io.Serializable;
import java.util.List;

/**
 * Raft消息基类
 * 
 * <p>定义了Raft算法中节点间通信的所有消息类型。Raft协议主要包含两种RPC：</p>
 * <ul>
 *   <li><b>RequestVote RPC</b>: 候选者发起选举时使用</li>
 *   <li><b>AppendEntries RPC</b>: Leader复制日志和发送心跳时使用</li>
 * </ul>
 * 
 * <p>所有消息都包含发送者的任期号和节点ID，这是Raft算法确保正确性的关键信息。</p>
 * 
 * @author Raft Implementation Team
 * @version 1.0
 * @since 1.0
 */
public abstract class RaftMessage implements Serializable {
    
    /** 序列化版本号 */
    private static final long serialVersionUID = 1L;
    
    /** 发送者的当前任期号 */
    protected final int term;
    
    /** 发送者的节点ID */
    protected final String senderId;
    
    /**
     * 构造Raft消息基类
     * 
     * @param term 发送者的当前任期号
     * @param senderId 发送者的节点ID，不能为null
     * @throws IllegalArgumentException 如果参数不合法
     */
    public RaftMessage(int term, String senderId) {
        if (term < 0) {
            throw new IllegalArgumentException("任期号不能为负数: " + term);
        }
        if (senderId == null || senderId.trim().isEmpty()) {
            throw new IllegalArgumentException("发送者ID不能为空");
        }
        
        this.term = term;
        this.senderId = senderId;
    }
    
    /**
     * 获取发送者的任期号
     * 
     * @return 发送者的当前任期号
     */
    public int getTerm() {
        return term;
    }
    
    /**
     * 获取发送者的节点ID
     * 
     * @return 发送者的节点ID
     */
    public String getSenderId() {
        return senderId;
    }
    
    /**
     * 投票请求消息 (RequestVote RPC)
     * 
     * <p>候选者在选举期间向其他节点发送此消息来请求投票。</p>
     * 
     * <p><b>投票规则：</b></p>
     * <ul>
     *   <li>接收者在当前任期还没有投票给其他候选者</li>
     *   <li>候选者的日志至少和接收者的日志一样新（安全性要求）</li>
     * </ul>
     * 
     * <p><b>日志新旧比较规则：</b></p>
     * <ol>
     *   <li>如果最后日志条目的任期号不同，任期号大的更新</li>
     *   <li>如果最后日志条目的任期号相同，日志更长的更新</li>
     * </ol>
     */
    public static class VoteRequest extends RaftMessage {
        
        /** 候选者最后一个日志条目的任期号 */
        private final int lastLogTerm;
        
        /** 候选者最后一个日志条目的索引 */
        private final int lastLogIndex;
        
        /**
         * 构造投票请求消息
         * 
         * @param term 候选者的当前任期号
         * @param candidateId 候选者的节点ID
         * @param lastLogTerm 候选者最后一个日志条目的任期号
         * @param lastLogIndex 候选者最后一个日志条目的索引
         */
        public VoteRequest(int term, String candidateId, int lastLogTerm, int lastLogIndex) {
            super(term, candidateId);
            this.lastLogTerm = lastLogTerm;
            this.lastLogIndex = lastLogIndex;
        }
        
        /**
         * 获取候选者最后一个日志条目的任期号
         * 
         * @return 最后日志条目的任期号
         */
        public int getLastLogTerm() { return lastLogTerm; }
        
        /**
         * 获取候选者最后一个日志条目的索引
         * 
         * @return 最后日志条目的索引
         */
        public int getLastLogIndex() { return lastLogIndex; }
    }
    
    /**
     * 投票响应消息 (RequestVote RPC Response)
     * 
     * <p>节点对候选者投票请求的响应消息。</p>
     * 
     * <p><b>投票决策逻辑：</b></p>
     * <ul>
     *   <li>如果候选者任期小于当前任期，拒绝投票</li>
     *   <li>如果当前任期已经投票给其他候选者，拒绝投票</li>
     *   <li>如果候选者日志不够新，拒绝投票</li>
     *   <li>否则，同意投票并重置选举超时计时器</li>
     * </ul>
     */
    public static class VoteResponse extends RaftMessage {
        
        /** 是否同意投票给候选者 */
        private final boolean voteGranted;
        
        /**
         * 构造投票响应消息
         * 
         * @param term 响应者的当前任期号
         * @param voterId 响应者的节点ID
         * @param voteGranted 是否同意投票
         */
        public VoteResponse(int term, String voterId, boolean voteGranted) {
            super(term, voterId);
            this.voteGranted = voteGranted;
        }
        
        /**
         * 检查是否同意投票
         * 
         * @return 如果同意投票返回true，否则返回false
         */
        public boolean isVoteGranted() { return voteGranted; }
    }
    
    /**
     * 附加日志请求消息 (AppendEntries RPC)
     * 
     * <p>Leader向Follower发送此消息来复制日志条目和发送心跳。</p>
     * 
     * <p><b>主要功能：</b></p>
     * <ul>
     *   <li><b>日志复制</b>: 当entries非空时，复制新的日志条目</li>
     *   <li><b>心跳</b>: 当entries为空时，作为心跳消息维持Leader权威</li>
     *   <li><b>日志一致性检查</b>: 通过prevLogIndex和prevLogTerm确保日志一致</li>
     * </ul>
     * 
     * <p><b>一致性检查机制：</b></p>
     * <p>Follower检查自己在prevLogIndex位置的日志条目是否具有prevLogTerm，
     * 如果不匹配则拒绝此请求，Leader会回退prevLogIndex重试。</p>
     * 
     * <p><b>提交机制：</b></p>
     * <p>leaderCommit告诉Follower哪些日志条目已经被Leader提交，
     * Follower会将这些条目应用到状态机。</p>
     */
    public static class AppendEntriesRequest extends RaftMessage {
        
        /** 新日志条目前一个条目的索引 */
        private final int prevLogIndex;
        
        /** 新日志条目前一个条目的任期号 */
        private final int prevLogTerm;
        
        /** 要存储的日志条目列表（心跳时为空） */
        private final List<LogEntry> entries;
        
        /** Leader已提交的日志条目索引 */
        private final int leaderCommit;
        
        /**
         * 构造附加日志请求消息
         * 
         * @param term Leader的当前任期号
         * @param leaderId Leader的节点ID
         * @param prevLogIndex 新日志条目前一个条目的索引
         * @param prevLogTerm 新日志条目前一个条目的任期号
         * @param entries 要存储的日志条目列表（可以为空，表示心跳）
         * @param leaderCommit Leader已提交的日志条目索引
         */
        public AppendEntriesRequest(int term, String leaderId, int prevLogIndex, 
                                  int prevLogTerm, List<LogEntry> entries, int leaderCommit) {
            super(term, leaderId);
            this.prevLogIndex = prevLogIndex;
            this.prevLogTerm = prevLogTerm;
            this.entries = entries;
            this.leaderCommit = leaderCommit;
        }
        
        /**
         * 获取前一个日志条目的索引
         * 
         * @return 前一个日志条目的索引
         */
        public int getPrevLogIndex() { return prevLogIndex; }
        
        /**
         * 获取前一个日志条目的任期号
         * 
         * @return 前一个日志条目的任期号
         */
        public int getPrevLogTerm() { return prevLogTerm; }
        
        /**
         * 获取要存储的日志条目列表
         * 
         * @return 日志条目列表，心跳时为空列表
         */
        public List<LogEntry> getEntries() { return entries; }
        
        /**
         * 获取Leader已提交的日志条目索引
         * 
         * @return Leader的提交索引
         */
        public int getLeaderCommit() { return leaderCommit; }
        
        /**
         * 检查此消息是否为心跳消息
         * 
         * @return 如果entries为空则返回true（心跳），否则返回false（日志复制）
         */
        public boolean isHeartbeat() {
            return entries == null || entries.isEmpty();
        }
    }
    
    /**
     * 附加日志响应消息 (AppendEntries RPC Response)
     * 
     * <p>Follower对Leader的AppendEntries请求的响应。</p>
     * 
     * <p><b>响应逻辑：</b></p>
     * <ul>
     *   <li><b>成功</b>: 日志一致性检查通过，成功附加日志条目</li>
     *   <li><b>失败</b>: 日志一致性检查失败，提供冲突索引帮助Leader快速回退</li>
     * </ul>
     * 
     * <p><b>冲突解决机制：</b></p>
     * <p>当success为false时，conflictIndex提供冲突位置信息，
     * Leader可以利用这个信息更快地找到Follower和自己日志的分歧点。</p>
     */
    public static class AppendEntriesResponse extends RaftMessage {
        
        /** 操作是否成功 */
        private final boolean success;
        
        /** 冲突索引（失败时用于快速回退） */
        private final int conflictIndex;
        
        /**
         * 构造附加日志响应消息
         * 
         * @param term 响应者的当前任期号
         * @param followerId 响应者的节点ID
         * @param success 操作是否成功
         * @param conflictIndex 冲突索引（成功时通常为-1）
         */
        public AppendEntriesResponse(int term, String followerId, boolean success, int conflictIndex) {
            super(term, followerId);
            this.success = success;
            this.conflictIndex = conflictIndex;
        }
        
        /**
         * 检查操作是否成功
         * 
         * @return 如果操作成功返回true，否则返回false
         */
        public boolean isSuccess() { return success; }
        
        /**
         * 获取冲突索引
         * 
         * <p>当操作失败时，此值指示日志冲突的位置，
         * Leader可以利用此信息快速调整nextIndex。</p>
         * 
         * @return 冲突索引，成功时通常为-1
         */
        public int getConflictIndex() { return conflictIndex; }
    }
}